using System.Linq.Expressions;

namespace MyMapster;

public class Helper {
  /// <summary>
  /// 可以处理复杂映射
  /// </summary>
  /// <typeparam name="TIn">输入类</typeparam>
  /// <typeparam name="TOut">输出类</typeparam>
  /// <param name="expression">表达式目录树,可以为null</param>
  /// <param name="tIn">输入实例</param>
  public static TOut Mapper<TIn, TOut> (TIn tIn, Expression<Func<TIn, TOut>> expression = null) {
    var memberBindingList = new List<MemberBinding>();
    var parameterExpression = Expression.Parameter(typeof(TIn), "p");

    if (expression != null) {
      parameterExpression = expression.Parameters[0];
      memberBindingList.AddRange(((MemberInitExpression)expression.Body).Bindings);
    }

    foreach (var item in typeof(TOut).GetProperties()) {
      if (typeof(TIn).GetProperty(item.Name) != null) {
        MemberExpression property =
            Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)!);
        MemberBinding memberBinding = Expression.Bind(item, property);
        memberBindingList.Add(memberBinding);
      }

      if (typeof(TIn).GetField(item.Name) == null) continue;
      {
        MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)!);
        MemberBinding memberBinding = Expression.Bind(item, property);
        memberBindingList.Add(memberBinding);
      }
    }

    foreach (var item in typeof(TOut).GetFields()) {
      if (typeof(TIn).GetField(item.Name) != null) {
        MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name)!);
        MemberBinding memberBinding = Expression.Bind(item, property);
        memberBindingList.Add(memberBinding);
      }

      if (typeof(TIn).GetProperty(item.Name) != null) {
        var property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name)!);
        MemberBinding memberBinding = Expression.Bind(item, property);
        memberBindingList.Add(memberBinding);
      }
    }

    var memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
    var lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, parameterExpression);
    var func = lambda.Compile();
    return func.Invoke(tIn);
  }
}